/**
 * @{#} LocationUtils.java Created on 2020年4月13日 下午5:54:07
 * <p>
 * Copyright (c) 2020 by SHUANGYI software.
 */
package com.jnks.utils;

import com.jnks.pojo.domain.dto.Point;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static java.lang.Math.sqrt;

public class LocationUtils {
    private static double EARTH_RADIUS = 6378.137;

    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }

    /**
     * 通过经纬度获取距离(单位：米)
     *
     * @param lat1 纬度1
     * @param lng1 经度1
     * @param lat2 纬度2
     * @param lng2 经度2
     * @return 距离
     */
    public static double getDistance(double lat1, double lng1, double lat2,
                                     double lng2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000d) / 10000d;
        s = s * 1000;
        return s;
    }

    /**
     * 判断坐标点是否在圆内(单位：米)
     *
     * @param radius 半径
     * @param lat1   纬度1
     * @param lng1   经度1
     * @param lat2   纬度2
     * @param lng2   经度2
     * @return 是否
     */
    public static boolean isInCircle(double radius, double lat1, double lng1, double lat2,
                                     double lng2) {
        Double dis = getDistance(lat1, lng1, lat2, lng2);
        if (dis <= radius) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 计算点到线的最短距离(单位：米)
     *
     * @param linePointOne 线的一号端点坐标
     * @param linePointTwo 线的二号端点坐标
     * @param from         点的坐标
     * @return 距离
     * x为lat，y为lng
     */
    public static double DistanceOfPointToLine(Point linePointOne, Point linePointTwo, Point from) {
        //求出三边长度
        double disLine = getDistance(linePointOne.x, linePointOne.y, linePointTwo.x, linePointTwo.y);
        double disOne = getDistance(linePointOne.x, linePointOne.y, from.x, from.y);
        double disTwo = getDistance(linePointTwo.x, linePointTwo.y, from.x, from.y);
        //判断当前三角形是否以LinePoint两点为最长边的三角形, 如果不是则,返回两点之间的最短距离
        if (Math.max(disLine, Math.max(disOne, disTwo)) != disLine) {
            return Math.min(disOne, disTwo);
        }
        //求出三角形面积
        double p = (disLine + disOne + disTwo) / 2;
        double s = sqrt(p * (p - disLine) * (p - disOne) * (p - disTwo));
        //返回Line的高即为点到线距离
        return 2 * s / disLine;
    }

    public static void main(String[] args) {
        // 地图上画一个多边形
        Point[] points = {
                new Point(120.320801, 30.081213),
                new Point(120.321852, 30.080335),
                new Point(120.321836, 30.081338),
                new Point(120.320801, 30.081213)

        };
        Point p = new Point(120.318907, 30.081839);

        // 当前点到多边形各边的距离
        System.out.println("distance=" + DistanceOfPointToLine(points[0], points[1], p) + "米");
        System.out.println("distance=" + DistanceOfPointToLine(points[1], points[2], p) + "米");
        System.out.println("distance=" + DistanceOfPointToLine(points[2], points[3], p) + "米");
    }

    /**
     * 多边形相交判断(有一个点相交也认为相交)
     *
     * @param pointList1 多边形1
     * @param pointList2 多边形2
     * @return boolean
     */
    public static boolean intersectionJudgment(List<Point> pointList1, List<Point> pointList2) {
        Polygon polygon1 = new Polygon(pointList1);
        Polygon polygon2 = new Polygon(pointList2);
        if (isExistNull(polygon1, polygon2)) {
            return false;
        }
        if (isExistNullOrEmpty(polygon1.getPoints(), polygon2.getPoints())) {
            return false;
        }
        // 1、快速判断，如果不想交，则返回不相交
        if (!fastExclude(polygon1, polygon2)) {
            return false;
        }
        // 获取多边形线段集合
        List<LineSegment> lineSegment1 = getLineSegment(polygon1);
        List<LineSegment> lineSegment2 = getLineSegment(polygon2);
        // 存在线段集合任意一方为空，则不想交
        if (isExistNullOrEmpty(lineSegment1, lineSegment2)) {
            return false;
        }
        // 2、向量叉乘判断多边形是否存在线段相交，存在相交则返回相交
        if (crossJudgment(lineSegment1, lineSegment2)) {
            return true;
        }
        // 3、包含关系判断，分别两次判断，判断polygon1是否在polygon2内部和polygon2是否在polygon1内部，满足其一即可
        boolean isInclude = includeRelation(polygon1, lineSegment2);
        if (isInclude) {
            return true;
        }
        return includeRelation(polygon2, lineSegment1);
    }

    /**
     * 1、快速判断多边形是否相交
     *
     * @param polygon1 多边形1
     * @param polygon2 多边形2
     * @return boolean
     */
    private static boolean fastExclude(Polygon polygon1, Polygon polygon2) {
        // 多边形1
        double polygon1MaxX = polygon1.getPoints().get(0).x;
        double polygon1MinX = polygon1.getPoints().get(0).x;
        double polygon1MaxY = polygon1.getPoints().get(0).y;
        double polygon1MinY = polygon1.getPoints().get(0).y;
        for (Point point : polygon1.getPoints()) {
            polygon1MaxX = Math.max(polygon1MaxX, point.x);
            polygon1MinX = Math.min(polygon1MinX, point.x);
            polygon1MaxY = Math.max(polygon1MaxY, point.y);
            polygon1MinY = Math.min(polygon1MinY, point.y);
        }

        // 多边形2
        double polygon2MaxX = polygon2.getPoints().get(0).x;
        double polygon2MinX = polygon2.getPoints().get(0).x;
        double polygon2MaxY = polygon2.getPoints().get(0).y;
        double polygon2MinY = polygon2.getPoints().get(0).y;
        for (Point point : polygon2.getPoints()) {
            polygon2MaxX = Math.max(polygon2MaxX, point.x);
            polygon2MinX = Math.min(polygon2MinX, point.x);
            polygon2MaxY = Math.max(polygon2MaxY, point.y);
            polygon2MinY = Math.min(polygon2MinY, point.y);
        }

        // 我这里是人为临界点的点-点，点-线也是属于相交，（如过你认为不是，加上等于条件，也就是最大和最小出现任意相等也是不想交）
        // 1、多边形1的最大x比多边形2最小x还小，说明多边形1在多边形2左边，不可能相交
        // 2、多边形1的最小x比多边形2最大x还大，说明多边形1在多边形2右边，不可能相交
        // 3、多边形1的最大y比多边形2最小y还小，说明多边形1在多边形2下边，不可能相交
        // 4、多边形1的最小y比多边形2最大y还小，说明多边形1在多边形2上边，不可能相交
        return !(polygon1MaxX < polygon2MinX)
                && !(polygon1MinX > polygon2MaxX)
                && !(polygon1MaxY < polygon2MinY)
                && !(polygon1MinY > polygon2MaxY);
    }

    /**
     * 获取线段集合
     *
     * @param polygon 多边形
     * @return 线段集合
     */
    private static List<LineSegment> getLineSegment(Polygon polygon) {
        List<LineSegment> lineSegments = new ArrayList<>();
        List<Point> points = polygon.getPoints();
        // 依次链接，形成线段
        for (int i = 0; i < points.size() - 1; i++) {
            Point previousElement = points.get(i);
            Point lastElement = points.get(i + 1);
            lineSegments.add(new LineSegment(previousElement, lastElement));
        }
        // 最后一组线段（最后一个点和初始点形成最后一条线段，形成闭环）
        if (lineSegments.size() > 0) {
            Point previousElement = points.get(points.size() - 1);
            Point lastElement = points.get(0);
            lineSegments.add(new LineSegment(previousElement, lastElement));
        }
        return lineSegments;
    }

    /**
     * 2、线段集合之间是否存在相交关系
     *
     * @param lineSegments1 线段集合1（其中一个多边形的线段集合）
     * @param lineSegments2 线段集合2（另一个多边形的线段集合）
     * @return boolean
     */
    private static boolean crossJudgment(List<LineSegment> lineSegments1, List<LineSegment> lineSegments2) {
        for (LineSegment lineSegment1 : lineSegments1) {
            for (LineSegment lineSegment2 : lineSegments2) {
                // 任意一组线段相交及多边形相交，返回相交
                if (calculationLineSegmentCrossing(lineSegment1, lineSegment2)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 线段是否相交（向量叉乘判断）
     *
     * @param lineSegment1 线段1
     * @param lineSegment2 线段2
     * @return boolean
     */
    private static boolean calculationLineSegmentCrossing(LineSegment lineSegment1, LineSegment lineSegment2) {
        // 如果存在任意一点在对方线段上，则相交
        if (isPointOnline(lineSegment1, lineSegment2)) {
            return true;
        }
        // 当不存端点在线段上，则进行交叉相交判断
        // A点
        Point aPoint = lineSegment1.getPrePoint();
        // B点
        Point bPoint = lineSegment1.getLastPoint();
        // C点
        Point cPoint = lineSegment2.getPrePoint();
        // D点
        Point dPoint = lineSegment2.getLastPoint();
        // AB向量叉乘AC向量
        double bc = crossProduct(aPoint, bPoint, aPoint, cPoint);
        // AB向量叉乘AD向量
        double bd = crossProduct(aPoint, bPoint, aPoint, dPoint);
        // CD向量叉乘CA向量
        double da = crossProduct(cPoint, dPoint, cPoint, aPoint);
        // CD向量叉乘CB向量
        double db = crossProduct(cPoint, dPoint, cPoint, bPoint);
        return bc * bd < 0 && da * db < 0;
    }

    /**
     * 两线段是否存在点在对方线段上
     *
     * @param lineSegment1 线段1
     * @param lineSegment2 线段2
     * @return boolean
     */
    private static boolean isPointOnline(LineSegment lineSegment1, LineSegment lineSegment2) {
        return isExistTrue(new boolean[]{
                isCollinearIntersection(lineSegment1.getPrePoint(), lineSegment2),
                isCollinearIntersection(lineSegment1.getLastPoint(), lineSegment2),
                isCollinearIntersection(lineSegment2.getPrePoint(), lineSegment1),
                isCollinearIntersection(lineSegment2.getLastPoint(), lineSegment1)});
    }

    /**
     * 点是否在线段上
     *
     * @param point            点
     * @param lineSegmentStart 线段起始点
     * @param lineSegmentEnd   线段尾点
     * @return boolean
     */
    private static boolean isCollinearIntersection(Point point, Point lineSegmentStart, Point lineSegmentEnd) {
        // 排除在延长线上的情况
        if (point.x >= Math.min(lineSegmentStart.x, lineSegmentEnd.x)
                && point.x <= Math.max(lineSegmentStart.x, lineSegmentEnd.x)
                && point.y >= Math.min(lineSegmentStart.y, lineSegmentEnd.y)
                && point.y <= Math.max(lineSegmentStart.y, lineSegmentEnd.y)) {
            // 任意两点之间形成的向量叉乘等于0，表示在线段上或延长线上（三点共线）
            return crossProduct(point, lineSegmentStart, point, lineSegmentEnd) == 0;
        }
        return false;
    }

    /**
     * 点是否在线段上
     *
     * @param point       点
     * @param lineSegment 线段
     * @return boolean
     */
    private static boolean isCollinearIntersection(Point point, LineSegment lineSegment) {
        return isCollinearIntersection(point, lineSegment.getPrePoint(), lineSegment.getLastPoint());
    }

    /**
     * 3、多边形polygon的所有点是都在另一个多边形内部（包含关系）
     *
     * @param polygon      被包含（内部）多边形
     * @param lineSegments 包含（外部）多边形所有线段集合
     * @return boolean
     */
    private static boolean includeRelation(Polygon polygon, List<LineSegment> lineSegments) {
        List<Point> points = polygon.getPoints();
        // 所有点在内部或者线段上才算包含，返回包含关系，只要一个不满足，则返回不包含关系
        for (Point point : points) {
            if (!pointInPolygon(point, lineSegments)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断某个点是否在多边形内部
     * ZX
     *
     * @param point        点
     * @param lineSegments 多边形线段集合
     * @return boolean
     */
    public static boolean pointInPolygon(Point point, List<LineSegment> lineSegments) {
        // 点坐标
        double x = point.x;
        double y = point.y;
        // 交点个数
        int intersectionNum = 0;
        // 判断射线与多边形的交点个数
        for (LineSegment seg : lineSegments) {
            // 如果点在多边形边线上，则算在多边形内部
            if (isCollinearIntersection(point, seg.getPrePoint(), seg.getLastPoint())) {
                return true;
            }
            double maxY = Math.max(seg.getPrePoint().y, seg.getLastPoint().y);
            double minY = Math.min(seg.getPrePoint().y, seg.getLastPoint().y);
            if (y >= minY && y < maxY) {
                // 计算交点X坐标
                double intersectionPointX = (y - seg.getPrePoint().y) * (seg.getLastPoint().x - seg.getPrePoint().x)
                        / (seg.getLastPoint().y - seg.getPrePoint().y) + seg.getPrePoint().x;
                if (x > intersectionPointX) {
                    intersectionNum++;
                }
            }
        }
        return intersectionNum % 2 != 0;
    }

    /**
     * 向量叉乘
     *
     * @param point1Start 向量1起点
     * @param point1End   向量1尾点
     * @param point2Start 向量2起点
     * @param point2End   向量2尾点
     * @return 向量叉乘结果
     */
    private static double crossProduct(Point point1Start, Point point1End, Point point2Start, Point point2End) {
        // 向量a
        double aVectorX = point1End.x - point1Start.x;
        double aVectorY = point1End.y - point1Start.y;
        // 向量b
        double bVectorX = point2End.x - point2Start.x;
        double bVectorY = point2End.y - point2Start.y;
        // 向量a叉乘向量b
        return aVectorX * bVectorY - bVectorX * aVectorY;
    }

    /**
     * 是否存在空对象
     *
     * @param objects 对象集合
     * @return boolean
     */
    private static boolean isExistNull(Object... objects) {
        for (Object object : objects) {
            if (object == null) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否存在空集合
     *
     * @param collections 集合对象
     * @return boolean
     */
    private static boolean isExistNullOrEmpty(Collection<?>... collections) {
        for (Collection<?> collection : collections) {
            if (collection == null || collection.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否存在true
     *
     * @param booleans 布尔集合
     * @return boolean
     */
    private static boolean isExistTrue(boolean[] booleans) {
        for (boolean bool : booleans) {
            if (bool) {
                return true;
            }
        }
        return false;
    }

    /**
     * 线段
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class LineSegment {
        private Point prePoint;
        private Point lastPoint;
    }

    /**
     * 多边形
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    private static class Polygon {
        private List<Point> points;
    }

    public static boolean isPtInPoly (double ALon , double ALat ,List<Point> ps) {
        int iSum, iCount, iIndex;
        double dLon1 = 0, dLon2 = 0, dLat1 = 0, dLat2 = 0, dLon;
        if (ps.size() < 3) {
            return false;
        }
        iSum = 0;
        iCount = ps.size();
        for (iIndex = 0; iIndex<iCount;iIndex++) {
            if (iIndex == iCount - 1) {
                dLon1 = ps.get(iIndex).getX();
                dLat1 = ps.get(iIndex).getY();
                dLon2 = ps.get(0).getX();
                dLat2 = ps.get(0).getY();
            } else {
                dLon1 = ps.get(iIndex).getX();
                dLat1 = ps.get(iIndex).getY();
                dLon2 = ps.get(iIndex + 1).getX();
                dLat2 = ps.get(iIndex + 1).getY();
            }
            // 以下语句判断A点是否在边的两端点的水平平行线之间，在则可能有交点，开始判断交点是否在左射线上
            if (((ALat >= dLat1) && (ALat < dLat2)) || ((ALat >= dLat2) && (ALat < dLat1))) {
                if (Math.abs(dLat1 - dLat2) > 0) {
                    //得到 A点向左射线与边的交点的x坐标：
                    dLon = dLon1 - ((dLon1 - dLon2) * (dLat1 - ALat) ) / (dLat1 - dLat2);
                    // 如果交点在A点左侧（说明是做射线与 边的交点），则射线与边的全部交点数加一：
                    if (dLon < ALon) {
                        iSum++;
                    }
                }
            }
        }
        if ((iSum % 2) != 0) {
            return true;
        }
        return false;
    }
}
